Introduction
Welcome!
Code Examples
Day 1
Challenge: KISS
Solution: KISS
Day 2
Challenge: Type Annotations
Solution: Type Annotations
Day 3
Challenge: Decoupling
Solution: Decoupling
Day 4
Challenge: DRY
Solution: DRY
Day 5
Challenge: String Formatting
Solution: String Formatting
Day 6
Challenge: Law Of Demeter
Solution: Law Of Demeter
Day 7
Challenge: Better Discounts
Solution: Better Discounts
Day 8
Challenge: Payment Strategy
Solution: Payment Strategy
Day 9
Challenge: Plugins
Solution: Plugins
Day 10
Challenge: Object Oriented To Functional
Solution: Object Oriented To Functional
Day 11
Challenge: Cohesion
Solution: Cohesion
Day 12
Challenge: MVP
Solution: MVP
Day 13
Challenge: Inheritance
Solution: Inheritance
Day 14
Challenge: Abstraction
Solution: Abstraction
Day 15
Challenge: Higher-Order Functions
Solution: Higher-Order Functions
Day 16
Challenge: Configuration
Solution: Configuration
Day 17
Challenge: Concurrency
Solution: Concurrency
Day 18
Challenge: Refactoring
Solution: Refactoring
Day 19
Challenge: Itertools
Solution: Itertools
Day 20
Challenge: Inappropriate Intimacy
Solution: Inappropriate Intimacy
Wrap Up
End of Part 1

Hi,
WeatherService can get callable which takes city and returns json with data (or even TypedDict).
This callable knows how to get the data, so API key is attached to the callable via partial function before it is injected into WeatherService init function. In that case we can easily exchange the source of weather forecast.
Best regards
Jarek
Thanks for your input!
Do you have any code examples?
Hi, I was thinking about this, here you are:
type Forecast = dict[str, Any]
type ForecastRetriever = Callable[[str], Forecast]
def OpenWeatherRetriever(city: str, key: str) -> Forecast:
url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={key}"
response = requests.get(url, timeout=5).json()
if "main" not in response:
raise CityNotFoundError(
f"Couldn't find weather data. Check '{city}' if it exists and is correctly spelled.\n"
)
return response
class WeatherService:
def __init__(self, retriever: ForecastRetriever) -> None:
self.full_weather_forecast: Forecast = {}
self.retriever = retriever
def retrieve_forecast(self, city: str) -> None:
self.full_weather_forecast = self.retriever(city)
# other methods
def main() -> None:
city = "Utrecht"
current_retriever = functools.partial(OpenWeatherRetriever, key=API_KEY)
client = WeatherService(retriever=current_retriever)
client.retrieve_forecast(city=city)
Best regards
Very nice, Jaroslaw!
Hi Andreas, how are you?
Here is my solution:
*************** Code *****************
from typing import Any
from abc import ABC, abstractmethod
from dataclasses import dataclass
import requests
API_KEY = "1234567"
class CityNotFoundError(Exception):
pass
class ApiGetter(ABC):
@abstractmethod
def fetch(self, url: str) -> dict[str, Any]:
"""Method that implements request library"""
@dataclass
class RequestApiGetter(ApiGetter):
timeout_value: int = 5
@property
def timeout(self) -> int:
return self.timeout_value
@timeout.setter
def timeout(self, value: int) -> None:
if isinstance(value, int):
self.timeout_value = value
else:
self.timeout_value = int(value)
def fetch(self, url: str) -> dict[str, Any]:
return requests.get(url, timeout=self.timeout_value).json()
class WeatherService:
def __init__(self, api_key: str, api_getter: ApiGetter) -> None:
self.api_key = api_key
self.api_getter = api_getter
self.full_weather_forecast: dict[str, Any] = {}
def retrieve_forecast(self, city: str) -> None:
url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}"
response = self.api_getter.fetch(url)
if "main" not in response:
raise CityNotFoundError(
f"Couldn't find weather data. Check '{city}' if it exists and is correctly spelled.\n"
)
self.full_weather_forecast = response
def main() -> None:
api_getter = RequestApiGetter()
city="Mar del Plata"
client = WeatherService(api_key=API_KEY, api_getter=api_getter)
client.retrieve_forecast(city=city)
temp = client.full_weather_forecast["main"]["temp"] - 273.15
hum = client.full_weather_forecast["main"]["humidity"]
wind_speed = client.full_weather_forecast["wind"]["speed"]
wind_direction = client.full_weather_forecast["wind"]["deg"]
print(f"The current temperature in {city} is {temp:.1f} °C.")
print(f"The current humidity in {city} is {hum}%.")
print(
f"The current wind speed in {city} is {wind_speed} m/s from direction {wind_direction} degrees."
)
if __name__ == "__main__":
main()
********** End Code***********
Have a nice day!
Alberto.
Nice solution!
It's good that you have introduced some abstractions with the ABC. If you want to improve this solution, add some properties for getting the temperature, wind, etc instead of using multiple nested keys.
I am in a bit of a dillemma with this. We can either: Make a generic http client and couple the rest of the code with the URL, or put the url inside a custom http client that manages the weather urls and their formatting. Which you would recommend in what situation?
I would recommend making a generic http client and then create an instance containing the url which then can be used where is needed. That way you can also set default header, tokens and more.
My take on it...
-------------
from dataclasses import dataclass
from typing import Any, Protocol
import requests
import tomllib
KELVIN_TO_CELSIUS_CONV = 273.15
with open("config.toml", "rb") as f:
config = tomllib.load(f)
class CityNotFoundError(Exception):
pass
def convert_kelvin_to_celsius(temp: float) -> float:
""" Convert temperature from Kelvin to Celsius """
return temp - KELVIN_TO_CELSIUS_CONV
# This might be too general
class RequestsClient(Protocol):
def get_data(self, *args, **kwargs) -> Any:
...
class WeatherForecastData(Protocol):
""" Only temps for now but could have other data points (humidity, wind direction) """
@property
def temp(self) -> float:
...
class WeatherService(Protocol):
def retrieve_forecast(self, city: str) -> WeatherForecastData:
""" Return city's forecast data """
...
def retrieve_forecast(city: str, ws: WeatherService) -> WeatherForecastData:
""" Retrieve forecast data for city with any defined weather api service """
return ws.retrieve_forecast(city)
def key_in_path(key_path: list[str], data: dict[str, Any]) -> bool:
""" Determine key path exists in dictionary """
next_dict = data
for key in key_path:
if key not in next_dict.keys():
return False
next_dict = next_dict[key]
return True
@dataclass
class HumanRequestsClient(RequestsClient):
def get_data(self, url: str, timeout: int) -> Any:
return requests.get(url, timeout=5) # I don't even check if this fails...lazy dude
@dataclass
class OpenWeatherMapForecastData(WeatherForecastData):
base_data: dict[str, Any]
@property
def temp(self) -> float:
if not key_in_path(config["OWM_TEMP_KEY_PATH"], self.base_data):
raise KeyError(f"Invalid key path ({','.join(config['OWM_TEMP_KEY_PATH'])}) used to retrieve temperature...check api reference")
data = self.base_data
last_key = config["OWM_TEMP_KEY_PATH"][-1:][0]
for key in config["OWM_TEMP_KEY_PATH"][:-1]:
data = data[key]
return convert_kelvin_to_celsius(data[last_key])
@dataclass
class OpenWeatherMapApi(WeatherService):
base_url: str
api_key: str
rc: RequestsClient
def retrieve_forecast(self, city: str) -> OpenWeatherMapForecastData:
url = f"{self.base_url}?q={city}&appid={self.api_key}"
response = self.rc.get_data(url, timeout=5).json()
if not key_in_path(key_path=config["OWM_ROOT_KEY_PATH"], data=response):
raise CityNotFoundError(
f"Couldn't find weather data. Check '{city}' if it exists and is correctly spelled.\n"
)
return OpenWeatherMapForecastData(response)
if __name__ == "__main__":
ow_service = OpenWeatherMapApi(
base_url=config["OWM_BASE_URL"],
api_key=config["OWM_API_KEY"],
rc=HumanRequestsClient()
)
city = "Utrecht"
wfd = retrieve_forecast(city, ow_service)
print(f"The current temperature in {city} is {wfd.temp:.1f} °C.")
Does `RequestsClient(HttpClient)` need to be inherited from `HttpClient` Protocol?
We could define plain RequestsClient class without inheritance which satisfies structural requirement (of HttpClient Protocol) to have `get` method right?
Hi Nipun, inheritance is not needed with protocol classes. Sometimes, it can still be handy though, especially if the classes are larger and the methods more complex. By inheriting explicitly instead of solely relying on duck typing, your IDE can perform some extra checks while you're writing the class to detect any mistakes you make when you're overriding the methods, arguments and return types.